package controller;

import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.JWTDecodeException;
import com.auth0.jwt.exceptions.JWTVerificationException;
import com.google.gson.Gson;
import com.google.gson.JsonArray;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import common.VerifyToken;
import dao.mysql.AccountMySQLDao;
import dao.mysql.VideoMySQLDao;
import entity.Account;
import entity.PlayCount;
import entity.Video;
import org.apache.avro.data.Json;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.web.servlet.MultipartConfigFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.stereotype.Controller;
import org.springframework.util.ClassUtils;
import org.springframework.util.ResourceUtils;
import org.springframework.web.bind.annotation.*;


import org.apache.log4j.Logger;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.multipart.MultipartFile;
import util.AnalysisUtil;
import util.IpUtil;
import util.NginxAnalysisUtil;
import util.TokenUtil;

import javax.servlet.MultipartConfigElement;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.net.URLDecoder;
import java.util.List;
import java.util.UUID;

@Controller
@EnableAutoConfiguration
public class MainController {
    private static String uploadPath = URLDecoder.decode(ClassUtils.getDefaultClassLoader().getResource("static/upload").getPath());
    private static Logger logger = Logger.getLogger(MainController.class);

    private void setResponseHeader(HttpServletResponse response) {
        response.setHeader("Access-Control-Allow-Origin", "*");
        response.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE, HEAD");
        response.setHeader("Access-Control-Allow-Headers", "access-control-allow-origin, authority, content-type, version-info, X-Requested-With");
    }

    private Object getDaoObject(String beanName) {
        return new ClassPathXmlApplicationContext("applicationContext.xml").getBean(beanName);
    }

    private JsonObject logInfoTemplate(HttpServletRequest request) {
        JsonObject logInfo = new JsonObject();
        String clientIP = IpUtil.getIpAddr(request);
        logInfo.addProperty("clientIP", clientIP);
        return logInfo;
    }

    private String verifyToken(HttpServletRequest request, HttpServletResponse response) throws Exception {
        String token = request.getHeader("Authorization");
        if (token == null) throw new Exception("Please login first.");
        String username;
        try {
            username = JWT.decode(token).getAudience().get(0);
        } catch (JWTDecodeException e) {
            response.setStatus(401);
            throw new Exception("Incorrect token.");
        }
        AccountMySQLDao accountHelper = (AccountMySQLDao) getDaoObject("AccountMySQLDao");
        Account a = accountHelper.queryAsEntity(username);
        if (a == null) {
            response.setStatus(401);
            throw new Exception("Account does not exist.");
        }
        JWTVerifier jwtVerifier =
                JWT.require(Algorithm.HMAC256(a.getD_password())).build();
        try {
            jwtVerifier.verify(token);
        } catch (JWTVerificationException e) {
            response.setStatus(401);
            throw new Exception("Token invalid. Please login again.");
        }
        return username;
    }

    public static void main(String[] args) throws Exception {
        SpringApplication.run(MainController.class, args);
    }

    // Test for server
    @RequestMapping("/")
    @ResponseBody
    @CrossOrigin
    public String index() {
        return "Saltybe - Share fun with the world";
    }

    /*
        Account API
        Author: @ReiKohaku
    */
    @RequestMapping(value = "/account/register", method = RequestMethod.POST)
    @ResponseBody
    @CrossOrigin
    public String accountRegister(@RequestBody String json, HttpServletRequest request, HttpServletResponse response) {
        setResponseHeader(response);
        JsonObject result = new JsonObject();
        JsonObject logInfo = logInfoTemplate(request);
        try {
            Account a = new Gson().fromJson(json, Account.class);
            AccountMySQLDao accountHelper = (AccountMySQLDao) getDaoObject("AccountMySQLDao");

            if (accountHelper.query(a.getD_username()) != null) throw new Exception("Username exists!");
            //So lazy that I don't want to write the format verify code again...
            //Add it by yourself plz

            accountHelper.add(a);
            result.addProperty("status", true);

            logInfo.addProperty("username", a.getD_username());
            logInfo.addProperty("email", a.getD_email());
            logger.info("[ Register ] " + new Gson().toJson(logInfo));
        } catch (Exception e) {
            result.addProperty("status", false);
            result.addProperty("error", e.getMessage());

            logInfo.addProperty("error", e.getMessage());
            logger.error("[ Register ] " + new Gson().toJson(logInfo));
        }
        return new Gson().toJson(result);
    }

    /*
      Some problems:
        * When user login at another device, the previous token still can be used
    */
    @RequestMapping(value = "/account/login", method = RequestMethod.POST)
    @ResponseBody
    @CrossOrigin
    public String accountLogin(@RequestBody String json, HttpServletRequest request, HttpServletResponse response) {
        setResponseHeader(response);
        JsonObject result = new JsonObject();
        JsonObject data = new JsonObject();
        JsonObject logInfo = logInfoTemplate(request);
        JsonObject requestForm = new JsonParser().parse(json).getAsJsonObject();
        try {
            if (requestForm.get("account") == null || requestForm.get("password") == null)
                throw new Exception("Form not correct!");
            AccountMySQLDao accountHelper = (AccountMySQLDao) getDaoObject("AccountMySQLDao");
            Account a = accountHelper.verify(requestForm.get("account").getAsString(), requestForm.get("password").getAsString());

            data.addProperty("token", TokenUtil.getToken(a.getD_username(), a.getD_password()));
            data.addProperty("username", a.getD_username());

            result.addProperty("status", true);
            result.add("data", data);

            logInfo.addProperty("account", requestForm.get("account").getAsString());
            logger.info("[ Login ] " + new Gson().toJson(logInfo));
        } catch (Exception e) {
            result.addProperty("status", false);
            result.addProperty("error", e.getMessage());

            logInfo.addProperty("error", e.getMessage());
            logger.error("[ Login ] " + new Gson().toJson(logInfo));
        }
        return new Gson().toJson(result);
    }

    @RequestMapping(value = "/account/exist")
    @ResponseBody
    @CrossOrigin
    public String accountExist(HttpServletRequest request, HttpServletResponse response) {
        setResponseHeader(response);
        JsonObject result = new JsonObject();
        JsonObject data = new JsonObject();
        try {
            AccountMySQLDao accountHelper = (AccountMySQLDao) getDaoObject("AccountMySQLDao");

            data.addProperty("has", accountHelper.query((String) request.getAttribute("username")) != null);

            result.addProperty("status", true);
            result.add("data", data);
        } catch (Exception e) {
            result.addProperty("status", false);
            result.addProperty("error", e.getMessage());
        }
        return new Gson().toJson(result);
    }

    @RequestMapping(value = "/account/isLogin", method = RequestMethod.POST)
    @ResponseBody
    @CrossOrigin
    public String accountIsLogin(HttpServletRequest request, HttpServletResponse response) {
        setResponseHeader(response);
        JsonObject result = new JsonObject();
        try {
            verifyToken(request, response);
            result.addProperty("status", true);
        } catch (Exception e) {
            result.addProperty("status", false);
            result.addProperty("error", e.getMessage());
        }
        return new Gson().toJson(result);
    }

    private String onUploadEvent(MultipartFile file, int maxSize, String[] accept, JsonObject logInfo) throws Exception {
        if (file.isEmpty()) throw new Exception("File is empty.");
        long fileSize = file.getSize();
        logInfo.addProperty("fileSize", fileSize);
        if (fileSize > maxSize) throw new Exception("File is too large.");
        String suffixName = file.getOriginalFilename().substring(file.getOriginalFilename().lastIndexOf("."));
        logInfo.addProperty("suffixName", suffixName);
        boolean isSupported = false;
        for (String s : accept) {
            if (suffixName.equals(s)) {
                isSupported = true;
                break;
            }
        }
        if (!isSupported) throw new Exception("File is not supported.");
        String fileName = UUID.randomUUID().toString().replace("-", "") + suffixName;
        logInfo.addProperty("fileName", fileName);
        String filePath = uploadPath + "/temp";
        File dest = new File(filePath, fileName);
        if (!dest.getParentFile().exists()) dest.getParentFile().mkdirs();
        file.transferTo(dest);
        return fileName;
    }

    private boolean checkTempFile(String tempFileName) {
        return new File(uploadPath + "/temp", tempFileName).exists();
    }

    private String saveTempFile(String tempFileName, String newType, String newPrefix) {
        File oldFile = new File(uploadPath + "/temp", tempFileName);
        File newFile = new File(uploadPath + "/" + newType, newPrefix + "_" + tempFileName);
        if (!newFile.getParentFile().exists()) newFile.getParentFile().mkdirs();
        oldFile.renameTo(newFile);
        return newPrefix + "_" + tempFileName;
    }

    @RequestMapping(value = "/account/upload/video", method = RequestMethod.POST)
    @ResponseBody
    @CrossOrigin
    public String uploadVideo(@RequestParam("videoFile") MultipartFile file, HttpServletRequest request, HttpServletResponse response) {
        setResponseHeader(response);
        JsonObject result = new JsonObject();
        JsonObject logInfo = logInfoTemplate(request);
        logInfo.addProperty("type", "video");
        int MAX_FILE_SIZE = 1024 * 1024 * 512;
        try {
            verifyToken(request, response);
            String fileName = onUploadEvent(file, MAX_FILE_SIZE, new String[] { ".mp4" }, logInfo);

            result.addProperty("status", true);
            result.addProperty("fileName", fileName);
            logger.info("[ Upload ] " + new Gson().toJson(logInfo));
        } catch (Exception e) {
            result.addProperty("status", false);
            result.addProperty("error", e.getMessage());

            logInfo.addProperty("error", e.getMessage());
            logger.error("[ Upload ] " + new Gson().toJson(logInfo));
            logger.error(e);
        }
        return new Gson().toJson(result);
    }

    @RequestMapping(value = "/account/upload/image", method = RequestMethod.POST)
    @ResponseBody
    @CrossOrigin
    public String uploadImage(@RequestParam("imageFile") MultipartFile file, HttpServletRequest request, HttpServletResponse response) {
        setResponseHeader(response);
        JsonObject result = new JsonObject();
        JsonObject logInfo = logInfoTemplate(request);
        logInfo.addProperty("type", "image");
        int MAX_FILE_SIZE = 1024 * 1024 * 2;
        try {
            verifyToken(request, response);
            String fileName = onUploadEvent(file, MAX_FILE_SIZE, new String[] { ".jpg", ".jpeg", ".png" }, logInfo);

            result.addProperty("status", true);
            result.addProperty("fileName", fileName);
            logger.info("[ Upload ] " + new Gson().toJson(logInfo));
        } catch (Exception e) {
            result.addProperty("status", false);
            result.addProperty("error", e.getMessage());

            logInfo.addProperty("error", e.getMessage());
            logger.error("[ Upload ] " + new Gson().toJson(logInfo));
        }
        return new Gson().toJson(result);
    }

    @RequestMapping(value = "/account/upload/new", method = RequestMethod.POST)
    @ResponseBody
    @CrossOrigin
    public String uploadNew(@RequestBody String json, HttpServletRequest request, HttpServletResponse response) throws Exception {
        setResponseHeader(response);
        JsonObject result = new JsonObject();
        JsonObject data = new JsonObject();
        JsonObject logInfo = logInfoTemplate(request);
        try {
            String username = verifyToken(request, response);

            Video v = new Gson().fromJson(json, Video.class);
            v.setD_username(username);

            if(!checkTempFile(v.getD_video()) || !checkTempFile(v.getD_cover())) throw new Exception("File not exist!");

            VideoMySQLDao videoHelper = (VideoMySQLDao) getDaoObject("VideoMySQLDao");

            int vid = videoHelper.add(v);
            saveTempFile(v.getD_video(), "video", Integer.toString(vid));
            saveTempFile(v.getD_cover(), "cover", Integer.toString(vid));

            data.addProperty("vid", vid);

            result.addProperty("status", true);
            result.add("data", data);
        } catch (Exception e) {
            result.addProperty("status", false);
            result.addProperty("error", e.getMessage());

            logInfo.addProperty("error", e.getMessage());
            logger.error("[ New video ] " + new Gson().toJson(logInfo));
        }
        return new Gson().toJson(result);
    }

    @RequestMapping(value = "/video/info", method = RequestMethod.POST)
    @ResponseBody
    @CrossOrigin
    public String videoInfo(@RequestBody String json, HttpServletRequest request, HttpServletResponse response) throws Exception {
        setResponseHeader(response);
        JsonObject result = new JsonObject();
        JsonObject data = new JsonObject();
        JsonObject logInfo = logInfoTemplate(request);
        JsonObject requestForm = new JsonParser().parse(json).getAsJsonObject();
        String username = null;
        try {
            username = verifyToken(request, response);
        } catch (Exception e) {
            username = IpUtil.getIpAddr(request);
        }
        try {
            VideoMySQLDao videoHelper = (VideoMySQLDao) getDaoObject("VideoMySQLDao");
            AccountMySQLDao accountHelper = (AccountMySQLDao) getDaoObject("AccountMySQLDao");

            Video v = videoHelper.query(requestForm.get("vid").getAsInt());
            logInfo.addProperty("vid", v.getD_id());
            logInfo.addProperty("username", username);
            data = new Gson().toJsonTree(v, Video.class).getAsJsonObject();
            data.add("user", accountHelper.userInfo(v.getD_username(), username));
            data.addProperty("playCount", videoHelper.playCount(v.getD_id()));
            data.addProperty("favorite", videoHelper.isFavorite(username, v.getD_id()));
            data.addProperty("favoriteCount", videoHelper.favoriteCount(v.getD_id()));

            result.addProperty("status", true);
            result.add("data", data);

            videoHelper.videoPlay(username, v.getD_id());

            logInfo.addProperty("id", v.getD_id());
            logInfo.addProperty("from", username);
            logger.info("[ Play video ] " + new Gson().toJson(logInfo));
        } catch (Exception e) {
            result.addProperty("status", false);
            result.addProperty("error", e.getMessage());

            logInfo.addProperty("error", e.getMessage());
            logger.error("[ Play video ] " + new Gson().toJson(logInfo));
        }
        return new Gson().toJson(result);
    }

    @RequestMapping(value = "/video/favorite", method = RequestMethod.POST)
    @ResponseBody
    @CrossOrigin
    public String videoFavorite(@RequestBody String json, HttpServletRequest request, HttpServletResponse response) throws Exception {
        setResponseHeader(response);
        JsonObject result = new JsonObject();
        JsonObject data = new JsonObject();
        JsonObject logInfo = logInfoTemplate(request);
        JsonObject requestForm = new JsonParser().parse(json).getAsJsonObject();
        try {
            String username = verifyToken(request, response);
            VideoMySQLDao videoHelper = (VideoMySQLDao) getDaoObject("VideoMySQLDao");

            videoHelper.setFavorite(username, requestForm.get("vid").getAsInt(), requestForm.get("favorite").getAsBoolean());
            result.addProperty("status", true);
        } catch (Exception e) {
            result.addProperty("status", false);
            result.addProperty("error", e.getMessage());

            logInfo.addProperty("error", e.getMessage());
            logger.error("[ Favorite ] " + new Gson().toJson(logInfo));
        }
        return new Gson().toJson(result);
    }

    @RequestMapping(value = "video/latest", method = RequestMethod.POST)
    @ResponseBody
    @CrossOrigin
    public String videoLatest(@RequestBody String json, HttpServletRequest request, HttpServletResponse response) throws Exception {
        /* This function should have a cache, add it by yourself plz */
        setResponseHeader(response);
        JsonObject result = new JsonObject();
        JsonArray data = new JsonArray();
        JsonObject logInfo = logInfoTemplate(request);
        JsonObject requestForm = new JsonParser().parse(json).getAsJsonObject();
        try {
            int count = 12;
            if (requestForm.get("count") != null) count = requestForm.get("count").getAsInt();
            VideoMySQLDao videoHelper = (VideoMySQLDao) getDaoObject("VideoMySQLDao");
            List<Video> list = videoHelper.latest(count);
            for (Video i : list) data.add(new Gson().toJsonTree(i, Video.class).getAsJsonObject());

            result.addProperty("status", true);
            result.add("data", data);
        } catch (Exception e) {
            result.addProperty("status", false);
            result.addProperty("error", e.getMessage());

            logInfo.addProperty("error", e.getMessage());
            logger.error("[ Latest ] " + new Gson().toJson(logInfo));
        }
        return new Gson().toJson(result);
    }

    @RequestMapping(value = "video/hot", method = RequestMethod.POST)
    @ResponseBody
    @CrossOrigin
    public String videoHot(@RequestBody String json, HttpServletRequest request, HttpServletResponse response) throws Exception {
        /* This function should have a cache, add it by yourself plz */
        setResponseHeader(response);
        JsonObject result = new JsonObject();
        JsonArray data = new JsonArray();
        JsonObject logInfo = logInfoTemplate(request);
        JsonObject requestForm = new JsonParser().parse(json).getAsJsonObject();
        try {
            int count = 12;
            if (requestForm.get("count") != null) count = requestForm.get("count").getAsInt();
            VideoMySQLDao videoHelper = (VideoMySQLDao) getDaoObject("VideoMySQLDao");
            List<PlayCount> list = videoHelper.hot(count);
            for (PlayCount i : list) data.add(new Gson().toJsonTree(videoHelper.query(i.getD_id()), Video.class).getAsJsonObject());

            result.addProperty("status", true);
            result.add("data", data);
        } catch (Exception e) {
            result.addProperty("status", false);
            result.addProperty("error", e.getMessage());

            logInfo.addProperty("error", e.getMessage());
            logger.error("[ Latest ] " + new Gson().toJson(logInfo));
        }
        return new Gson().toJson(result);
    }

    @RequestMapping(value = "video/search", method = RequestMethod.POST)
    @ResponseBody
    @CrossOrigin
    public String videoSearch(@RequestBody String json, HttpServletRequest request, HttpServletResponse response) throws Exception {
        /* This function should have a cache, add it by yourself plz */
        setResponseHeader(response);
        JsonObject result = new JsonObject();
        JsonArray data = new JsonArray();
        JsonObject logInfo = logInfoTemplate(request);
        JsonObject requestForm = new JsonParser().parse(json).getAsJsonObject();
        try {
            JsonArray keys = requestForm.get("key").getAsJsonArray();
            logInfo.add("key", keys);
            VideoMySQLDao videoHelper = (VideoMySQLDao) getDaoObject("VideoMySQLDao");
            String[] sKeys = new Gson().fromJson(keys, String[].class);
            List<Video> list = videoHelper.search(sKeys);
            for (Video v : list) data.add(new Gson().toJsonTree(v, Video.class).getAsJsonObject());

            result.addProperty("status", true);
            result.add("data", data);

            logger.info("[ Search ] " + new Gson().toJson(logInfo));
        } catch (Exception e) {
            result.addProperty("status", false);
            result.addProperty("error", e.getMessage());

            logInfo.addProperty("error", e.getMessage());
            logger.error("[ Search ] " + new Gson().toJson(logInfo));

            e.printStackTrace();
        }
        return new Gson().toJson(result);
    }

    @RequestMapping(value = "log", method = RequestMethod.GET)
    @ResponseBody
    @CrossOrigin
    public String logParse(HttpServletRequest request, HttpServletResponse response) throws Exception {
        setResponseHeader(response);
        JsonObject result = new JsonObject();
        try {
            result.add("data", AnalysisUtil.AppLogAnalysis());
            result.addProperty("status", true);
        } catch (Exception e) {
            result.addProperty("status", false);
            result.addProperty("error", e.getMessage());
        }
        return new Gson().toJson(result);
    }

    private JsonObject analysisResult = null;
    @RequestMapping(value = "log/nginx", method = RequestMethod.GET)
    @ResponseBody
    @CrossOrigin
    public String NginxLogParse(HttpServletRequest request, HttpServletResponse response) throws Exception {
        setResponseHeader(response);
        JsonObject result = new JsonObject();
        try {
            if(analysisResult == null) analysisResult = NginxAnalysisUtil.NginxLogAnalysis();
            result.add("data", analysisResult);
            result.addProperty("status", true);
        } catch (Exception e) {
            result.addProperty("status", false);
            result.addProperty("error", e.getMessage());
        }
        return new Gson().toJson(result);
    }
}
